using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class PadFrame : RapidHardware 
    {
        Dictionary<int, string> disInputPins = new Dictionary<int, string>();
        Dictionary<int, string> disOutputPins = new Dictionary<int, string>();
        int iGndPin = 0;
        int iVDDPin = 0;

        private void CheckPinAvailable(int piPin)
        {
            if (piPin <= 0 || piPin > 40)
                throw new Exception("Pin " + piPin.ToString() + " is not between 0 and 40!");

            if (disInputPins.ContainsKey(piPin))
                throw new Exception("Pin " + piPin.ToString() + " is already assigned to " + disInputPins[piPin] + ".");

            if (disOutputPins.ContainsKey(piPin))
                throw new Exception("Pin " + piPin.ToString() + " is already assigned to " + disOutputPins[piPin] + ".");

            if (iGndPin == piPin)
                throw new Exception("Pin " + piPin.ToString() + " is already assigned to GND.");

            if (iVDDPin == piPin)
                throw new Exception("Pin " + piPin.ToString() + " is already assigned to VDD.");
        }

        private void AddInputPin(int piPin, string psBit)
        {
            CheckPinAvailable(piPin);
            disInputPins.Add(piPin, psBit);
        }

        private void AddOutputPin(int piPin, string psBit)
        {
            CheckPinAvailable(piPin);
            disOutputPins.Add(piPin, psBit);
        }

        public void AssignPinOutput(string psSignalView, int piStartPin)
        {
            SignalView oSignal = SystemInterface.GetSignalView(psSignalView);
            if (oSignal.Width == 1)
            {
                AddOutputPin(piStartPin, oSignal.NameHDL);
                return;
            }

            int iPin = piStartPin;
            for (int iBit = 0; iBit < oSignal.Width; iBit++)
            {
                AddOutputPin(iPin, oSignal.NameHDL + "[" + iBit.ToString() + "]");
                iPin++;
            }
                
        }

        public void AssignPinInput(string psSignalSource, int piStartPin)
        {
            SignalSource oSignal = SystemInterface.GetSignalSource(psSignalSource);
            if (oSignal.Width == 1)
            {
                AddInputPin(piStartPin, oSignal.NameHDL);
                return;
            }

            int iPin = piStartPin;
            for (int iBit = 0; iBit < oSignal.Width; iBit++)
            {
                AddInputPin(iPin, oSignal.NameHDL + "[" + iBit.ToString() + "]");
                iPin++;
            }
        }

        public void AssignPinGND(int piPin)
        {
            CheckPinAvailable(piPin);
            iGndPin = piPin;
        }

        public void AssignPinVDD(int piPin)
        {
            CheckPinAvailable(piPin);
            iVDDPin = piPin;
        }

        public void AssignPinCLK(int piPin)
        {
            AddInputPin(piPin, "CLK_I");
        }

        public void AssignPinRESET(int piPin)
        {
            AddInputPin(piPin, "RESET_I");
        }

        public void WritePadframeVerilog()
        {
            int iSourceCount = SystemInterface.SignalSources.Count - 1;
            int iViewCount = SystemInterface.SignalViews.Count - 1;
            
            if (iSourceCount + iViewCount <= 0)
                return;

            if (Settings.UseFIFO || Settings.UseSDRAM || Settings.UseTestBus)
            {
                throw new Exception("UseFIFO, UseSDRAM, UseTestBus settings are not supported.");
            }

            ScriptHelper oScript = new ScriptHelper(this);
            oScript.NewFile("PINS.v");
            
            string sVerilog = "module PINS(";
            sVerilog = oScript.BuildString(sVerilog, "CLK_I,", 1);
            if (Settings.UseReset)
            {
                sVerilog = oScript.BuildString(sVerilog, "RESET_I,", 1);
            }
            foreach (SignalSource oSignal in SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, oSignal.NameHDL + ",", 1);
            }

            foreach (SignalView oSignal in SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, oSignal.NameHDL + ",", 1);
            }

            sVerilog = oScript.RemoveStringEnding(sVerilog, 1);
            oScript.WriteLine(sVerilog);
            oScript.WriteLine(");");

            sVerilog = "";
            sVerilog = oScript.BuildString(sVerilog, "input CLK_I;", 1);
            if (Settings.UseReset)
            {
                sVerilog = oScript.BuildString(sVerilog, "input RESET_I;", 1);
            }
            foreach (SignalSource oSignal in SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, "input " + oScript.VerilogVector(oSignal.NameHDL, oSignal.Width) + ";", 1);
            }

            foreach (SignalView oSignal in SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                    sVerilog = oScript.BuildString(sVerilog, "output " + oScript.VerilogVector(oSignal.NameHDL, oSignal.Width) + ";", 1);
            }
            oScript.WriteLine(sVerilog);

            oScript.BlankLine();
            sVerilog = "";
            sVerilog = oScript.BuildString(sVerilog, "wire w_clk_i;", 1);
            if (Settings.UseReset)
            {
                sVerilog = oScript.BuildString(sVerilog, "wire w_reset_i;", 1);
            }
            foreach (SignalSource oSignal in SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {

                    if (oSignal.Width > 1)
                    {
                        int iStart = oSignal.Width - 1;
                        sVerilog = oScript.BuildString(sVerilog, "wire [" + iStart.ToString() + ":0] w_" + oSignal.NameHDL.ToLower() + ";", 1);
                    }
                    else
                        sVerilog = oScript.BuildString(sVerilog, "wire w_" + oSignal.NameHDL.ToLower() + ";", 1);
                }
            }

            foreach (SignalView oSignal in SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {

                    if (oSignal.Width > 1)
                    {
                        int iStart = oSignal.Width - 1;
                        sVerilog = oScript.BuildString(sVerilog, "wire [" + iStart.ToString() + ":0] w_" + oSignal.NameHDL.ToLower() + ";", 1);
                    }
                    else
                        sVerilog = oScript.BuildString(sVerilog, "wire w_" + oSignal.NameHDL.ToLower() + ";", 1);
                }
            }
            oScript.WriteLine(sVerilog);


            oScript.BlankLine();
            oScript.WriteLine("TOP top");
            sVerilog = "(";

            sVerilog = oScript.BuildString(sVerilog, ".CLK_I(w_clk_i),",1);
            if (Settings.UseReset)
                sVerilog = oScript.BuildString(sVerilog, ".RESET_I(w_reset_i),",1);
            foreach (SignalSource oSignal in SystemInterface.SignalSources.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    sVerilog = oScript.BuildString(sVerilog, "." + oSignal.NameHDL + "(w_" + oSignal.NameHDL.ToLower() + "),", 1);
                }
            }
            foreach (SignalView oSignal in SystemInterface.SignalViews.Values)
            {
                if (!oSignal.IsIOTest)
                {
                    NodeVector oTopLevelOutput = oSignal.GetTopLevelComponentOutput();
                    sVerilog = oScript.BuildString(sVerilog, "." + oSignal.NameHDL + "(w_" + oSignal.NameHDL.ToLower() + "),", 1);
                }
            }
            sVerilog = oScript.RemoveStringEnding(sVerilog, 1);
            oScript.WriteLine(sVerilog);
            oScript.WriteLine(" );");
            
            oScript.BlankLine();

            foreach (KeyValuePair<int,string> oPair in disInputPins)
            {
                WriteInputPin(oPair.Key, oPair.Value, oScript);
            }

            oScript.BlankLine();

            foreach (KeyValuePair<int, string> oPair in disOutputPins)
            {
                WriteOutputPin(oPair.Key, oPair.Value, oScript);
            }

            oScript.BlankLine();

            WritePowerPin(iVDDPin, "vdd", oScript);

            oScript.BlankLine();

            WritePowerPin(iGndPin, "gnd", oScript);

            oScript.BlankLine();
            oScript.WriteLine("endmodule");
            oScript.CloseFile();
        }

        private void WriteInputPin(int piPin, string psName, ScriptHelper poScript)
        {
            string sVerilog = "wire dummy" + piPin.ToString() + ";";
            sVerilog = poScript.BuildString(sVerilog,"PadInC PIN" + piPin.ToString() + "(",0);
            sVerilog = poScript.BuildString(sVerilog, ".Pad(" + psName + "),", 1);
            sVerilog = poScript.BuildString(sVerilog, ".DataIn(" + "w_" + psName.ToLower() + "),", 1);
            sVerilog = poScript.BuildString(sVerilog, ".DataInB(" + "dummy" + piPin.ToString() + ")", 1);
            sVerilog = poScript.BuildString(sVerilog, ");", 1);
            poScript.WriteLine(sVerilog);
        }


        private void WriteOutputPin(int piPin, string psName, ScriptHelper poScript)
        {
            string sVerilog = "PadOut PIN" + piPin.ToString() + "(";
            sVerilog = poScript.BuildString(sVerilog, ".Pad(" + psName + "),", 1);
            sVerilog = poScript.BuildString(sVerilog, ".DataOut(" + "w_" + psName.ToLower() + ")", 1);
            sVerilog = poScript.BuildString(sVerilog, ");", 1);
            poScript.WriteLine(sVerilog);
        }

        private void WritePowerPin(int piPin, string psName, ScriptHelper poScript)
        {
            string sLower = psName.ToLower();
            string sILower = "Gnd";
            if (sLower == "vdd")
                sILower = "Vdd";

            string sVerilog = "wire w_" + sLower + ";";
            sVerilog = poScript.BuildString(sVerilog, "fake_" + sLower + " " + sLower + "_fake(.Y(w_" + sLower + "));", 0);
            sVerilog = poScript.BuildString(sVerilog, "Pad" + sILower + " PIN" + piPin.ToString() + "(", 0);
            sVerilog = poScript.BuildString(sVerilog, "." + sILower + "(w_" + sLower + ")", 1);
            sVerilog = poScript.BuildString(sVerilog, ");", 1);
            poScript.WriteLine(sVerilog);
        }

        private void WriteGNDPin(int piPin, string psName, ScriptHelper poScript)
        {
            string sLower = psName.ToLower();
            string sILower = "Gnd";
            if (sLower == "vdd")
                sILower = "Vdd";

            string sVerilog = "wire w_" + sLower + ";";
            sVerilog = poScript.BuildString(sVerilog, "fake_" + sLower + " " + sLower + "_fake(.Y(w_" + sLower + "));", 0);
            sVerilog = poScript.BuildString(sVerilog, "Pad" + sILower + " PIN" + piPin.ToString() + "(", 0);
            sVerilog = poScript.BuildString(sVerilog, "." + sILower + "(w_" + sLower + ")", 1);
            sVerilog = poScript.BuildString(sVerilog, ");", 1);
            poScript.WriteLine(sVerilog);
        }



        public override bool EventHandler(RapidHardwareEventTypes pEventType, object oParam1, object oParam2)
        {
            if (pEventType == RapidHardwareEventTypes.HardwareStarted)
            {
                WritePadframeVerilog();
            }
            return base.EventHandler(pEventType, oParam1, oParam2);
        }


    }
}
